/*!
    \copyright  Copyright (c) 2021-  2024 Qualcomm Technologies International, Ltd.\n
                All Rights Reserved.\n
                Qualcomm Technologies International, Ltd. Confidential and Proprietary.
    \file
    \ingroup    handset_service
    \brief      Handset service BLE state machine
*/

#include <bdaddr.h>
#include <device.h>
#include <device_properties.h>
#include <pairing.h>

#include "handset_service_ble_sm.h"
#include "handset_service_connectable.h"
#include "handset_service_sm.h"
#include "handset_service_protected.h"
#include "handset_service_acceptor_list.h"

#include "device_db_serialiser.h"

#ifndef INCLUDE_LEGACY_LE_ADVERTISING_MANAGER
#include "handset_service_advertising.h"
#else
#include "handset_service_legacy_advertising.h"
#include "handset_service_extended_advertising.h"
#endif

#include "context_framework.h"

static bool handsetService_BredrSmInactive(handset_service_ble_state_machine_t* ble_sm)
{
    handset_service_state_machine_t *sm = HandsetService_GetSmFromBleSm(ble_sm);

    /* The disconnect list is not a blocker because clearing this list
     * is one of the final things we do before destroying the SM. */
    return (sm == NULL) || (sm->state <= HANDSET_SERVICE_STATE_DISCONNECTED &&
                            TaskList_Size(&sm->connect_list) == 0 &&
                            TaskList_Size(&sm->connect_stop_list) == 0);
}

static void handsetService_BleSmEnterDisconnecting(handset_service_ble_state_machine_t* ble_sm)
{
    uint8 hci_disconnect_reason_code = HandsetService_Get()->disconnect_reason_code;
    handset_service_state_machine_t *sm = HandsetService_GetSmFromBleSm(ble_sm);

    /* Remove LE ACL */
    if (!BdaddrTpIsEmpty(&ble_sm->le_addr))
    {
        if (sm != NULL && sm->disconnect_reason_code != HANDSET_SERVICE_INVALID_REASON_CODE)
        {
            hci_disconnect_reason_code = sm->disconnect_reason_code;
        }
#ifdef ENABLE_LE_CONNECTION_BARGE_IN
        if (!BdaddrTpIsEmpty(&HandsetService_Get()->barge_in_address))
        {
            /* Disconnection is due to barge in */
            hci_disconnect_reason_code = HANDSET_SERVICE_BARGE_IN_DISCONNECT_HCI_REASON_CODE;
        }
#endif /* ENABLE_LE_CONNECTION_BARGE_IN */

        ConManagerReleaseTpAclWithReasonCode(&ble_sm->le_addr, hci_disconnect_reason_code);
    }
    else
    {
        /* We did not have anything to disconnect. 
           May be a disconnect message in flight, or a bug.
           Change state. uses recursion, but one level only */
        HandsetService_BleSmSetState(ble_sm, HANDSET_SERVICE_STATE_BLE_DISCONNECTED);
    }
}

static bool handsetService_BleSmDeviceIsPaired(const handset_service_ble_state_machine_t *ble_sm)
{
    uint16 flags = DEVICE_FLAGS_NO_FLAGS;
    bool is_paired = FALSE;

    appDeviceGetFlagsForDevice(BtDevice_GetDeviceFromTpAddr(&ble_sm->le_addr), &flags);

    if ((flags & DEVICE_FLAGS_NOT_PAIRED) != DEVICE_FLAGS_NOT_PAIRED)
    {
        is_paired = TRUE;
    }

    return is_paired;
}

static void handsetService_BleSmDeleteDeviceIfNotPaired(handset_service_ble_state_machine_t *sm)
{
    if (!handsetService_BleSmDeviceIsPaired(sm))
    {
        HS_LOG("handsetService_BleSmDeleteDeviceIfNotPaired delete device");
        /* Delete the device as not paired */
        appDeviceDeleteWithTpAddr(&sm->le_addr);
    }
}

static void handsetService_BleSmEnterDisconnected(handset_service_ble_state_machine_t* ble_sm)
{
    handset_service_state_machine_t *sm = HandsetService_GetSmFromBleSm(ble_sm);
    bool all_sm_transports_disconnected = HandsetServiceSm_AllConnectionsDisconnected(sm, BREDR_AND_BLE);
    bool bredr_sm_inactive = handsetService_BredrSmInactive(ble_sm);

    device_t handset_device = HandsetServiceSm_GetHandsetDeviceIfValid(sm);
    if (all_sm_transports_disconnected && bredr_sm_inactive)
    {
        HandsetServiceSm_CompleteDisconnectRequests(sm, handset_service_status_success);

        handsetService_RestoreOtherConnectedHandsetsToMru(handset_device);
    }

    if (handset_device)
    {
        DeviceDbSerialiser_SerialiseDevice(sm->handset_device);
    }

#ifdef INCLUDE_LEGACY_LE_ADVERTISING_MANAGER
    HandsetService_UpdateLegacyAdvertisingData();
    UNUSED(HandsetServiceExtAdv_UpdateAdvertisingData());
#endif
    
    if (HandsetService_ConsolidatedHandsetCountEnabled())
    {
#ifndef ENABLE_LEA_TARGETED_ANNOUNCEMENT
        handsetService_AcceptorListRemoveHandset(&ble_sm->le_addr);
#else
        handset_ble_context_t ble_context = DeviceProperties_GetHandsetBleContext(sm->handset_device);

        if (BtDevice_IsLeAudioDevice(sm->handset_device) &&
           ((ble_context == handset_ble_context_link_loss) || (ble_context == handset_ble_context_link_loss_streaming)))
        {
            DEBUG_LOG_VERBOSE("handsetService_BleSmEnterDisconnected keep device %p in acceptor list due to link loss ble_context %d", sm->handset_device, ble_context);
        }
        else
        {
            handsetService_AcceptorListRemoveHandset(&ble_sm->le_addr);
        }
#endif
    }

    if (all_sm_transports_disconnected && bredr_sm_inactive)
    {
        if (sm->handset_device != NULL)
        {
            HandsetService_SendAllTransportsDisconnectedIndNotification(&ble_sm->le_addr);
        }

        HS_LOG("handsetService_BleSmEnterDisconnected destroying sm for dev 0x%x", sm->handset_device);
        handsetService_BleSmDeleteDeviceIfNotPaired(ble_sm);
        HandsetServiceSm_DeInit(sm);
    }
    else
    {
        HS_LOG("handsetService_BleSmEnterDisconnected destroying only BLE sm for dev 0x%x", sm->handset_device);
        HandsetService_BleSmReset(ble_sm);
    }

    appLinkPolicyUpdatePowerTable(&ble_sm->le_addr.taddr.addr);
}

void HandsetService_BleSmReset(handset_service_ble_state_machine_t* ble_sm)
{
    BdaddrTpSetEmpty(&ble_sm->le_addr);
    ble_sm->le_state = HANDSET_SERVICE_STATE_BLE_DISCONNECTED;
}

void HandsetService_BleSmSetState(handset_service_ble_state_machine_t* ble_sm, handset_service_ble_state_t state)
{
    handset_service_ble_state_t old_state = ble_sm->le_state;

    HS_LOG("HandsetService_BleSmSetState enum:handset_service_ble_state_t:%d -> enum:handset_service_ble_state_t:%d", old_state, state);

    ble_sm->le_state = state;

    if(old_state != HANDSET_SERVICE_STATE_BLE_DISCONNECTING && state == HANDSET_SERVICE_STATE_BLE_DISCONNECTING)
    {
        handsetService_BleSmEnterDisconnecting(ble_sm);
    }

    if(old_state != HANDSET_SERVICE_STATE_BLE_DISCONNECTED && state == HANDSET_SERVICE_STATE_BLE_DISCONNECTED)
    {
        handsetService_BleSmEnterDisconnected(ble_sm);
    }

#ifndef INCLUDE_LEGACY_LE_ADVERTISING_MANAGER
    if(old_state != state && (state == HANDSET_SERVICE_STATE_BLE_CONNECTED || state == HANDSET_SERVICE_STATE_BLE_DISCONNECTED))
    {
        handsetService_UpdateConnectable();
    }
#endif
    ContextFramework_NotifyContextUpdate(context_connected_handsets_info);
}

bool HandsetService_BleIsConnected(handset_service_ble_state_machine_t* ble_sm)
{
    PanicNull(ble_sm);
    
    switch(ble_sm->le_state)
    {
        case HANDSET_SERVICE_STATE_BLE_CONNECTED:
        case HANDSET_SERVICE_STATE_BLE_DISCONNECTING:
        {
            if (!BdaddrTpIsEmpty(&ble_sm->le_addr) && ConManagerIsTpConnected(&ble_sm->le_addr))
            {
                return TRUE;
            }
            else
            {
                return FALSE;
            }
        }
        
        default:
        {
            return FALSE;
        }
    }
}

void HandsetService_BleDisconnectIfConnected(handset_service_ble_state_machine_t* ble_sm)
{
    if (HandsetService_BleIsConnected(ble_sm))
    {
        HandsetService_BleSmSetState(ble_sm, HANDSET_SERVICE_STATE_BLE_DISCONNECTING);
    }
}

static bool handsetService_BleConnectionAllowed(const handset_service_ble_state_machine_t* ble_sm)
{
    bool connection_allowed = FALSE;

    if (HandsetService_IsPairing())
    {
        connection_allowed = TRUE;
    }
    else if (HandsetService_ConsolidatedHandsetCountEnabled())
    {
        /* To be able to connect a handset:
            * There is space in the handset service acceptor list for it
              Note: if it is already connected for BR/EDR then it shall be
                    allowed to connect LE. */
        if (handsetService_AcceptorListCanAddHandset(&ble_sm->le_addr))
        {
            connection_allowed = TRUE;
        }
    }
    else
    {
        connection_allowed = TRUE;
    }

    return connection_allowed;
}

void HandsetService_BleHandleConnected(handset_service_ble_state_machine_t* ble_sm, tp_bdaddr* tpaddr)
{
    handset_service_state_machine_t* sm = HandsetService_GetSmFromBleSm(ble_sm);

    if (BdaddrTpIsEmpty(&ble_sm->le_addr))
    {
        ble_sm->le_addr = *tpaddr;
    }

    if (handsetService_BleConnectionAllowed(ble_sm))
    {
        device_t device = BtDevice_GetDeviceForTpbdaddr(tpaddr);

        if (HandsetService_ConsolidatedHandsetCountEnabled())
        {
            if (handsetService_BleSmDeviceIsPaired(ble_sm))
            {
                handsetService_AcceptorListAddHandset(tpaddr);
            }
        }

        HandsetService_BleSmSetState(ble_sm, HANDSET_SERVICE_STATE_BLE_CONNECTED);

        /* We might get BLE connection before BR/EDR connection, but it could so happen that
           we are in middle of pairing, so we end up triggering First Transport connected indication.
           Impact is that "connection" prompt will be followed by "pairing successful" or worst
           "pairing failed". This gives bad user behavior. So, we need to avoid sending the notification
           if pairing is still active */
        if (device != NULL
            && BtDevice_GetDeviceType(device) == DEVICE_TYPE_HANDSET
            && (!HandsetServiceSm_IsBredrAclConnected(sm) || !sm->first_bredr_profile_connected)
            && PairingIsIdle())
        {
            /* Send the first tranport connected notification to play the connected prompt */
            HandsetService_SendFirstTransportConnectedIndNotification(tpaddr);
        }
    }
    else
    {
        DEBUG_LOG_WARN("HandsetService_BleHandleConnected WARNING: handset [0x%01x %04x,%02x,%06lx] not allowed to connect",
                       ble_sm->le_addr.taddr.type,
                       ble_sm->le_addr.taddr.addr.nap,
                       ble_sm->le_addr.taddr.addr.uap,
                       ble_sm->le_addr.taddr.addr.lap);

        HandsetService_BleSmSetState(ble_sm, HANDSET_SERVICE_STATE_BLE_DISCONNECTING);
    }

    appLinkPolicyUpdatePowerTable(&ble_sm->le_addr.taddr.addr);
}
